package game;

import cards.Card;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.logging.Logger;

public class Player {
  private static final Logger logger = Logger.getLogger(Player.class.getName());

  private int id;
  private String name;
  private List<CardInfo> cards;
  private int lastCardIndex = -1;

  /**
   * Contextual card information relative to a player's hand.
   */
  static class CardInfo {
    private int index = -1;
    private Card card;
    private boolean visible;

    public CardInfo(Card card, boolean visible) {
      this.card = card;
      this.visible = visible;
    }

    public boolean getVisibility() {
      return visible;
    }

    public void setVisibility(boolean visible) {
      this.visible = visible;
    }

    public Card getCard() {
      return card;
    }

    public int getIndex() {
      return index;
    }

    private void setIndex(int index) {
      this.index = index;
    }

    public int hashCode() {
      return index * 31 + card.hashCode();
    }

    public boolean equals(Object obj) {
      if (obj instanceof CardInfo) {
        CardInfo ci = (CardInfo)obj;
        return ci.index == this.index && ci.card.equals(this.card);
      }
      return false;
    }

    public String toString() {
      StringBuilder sb = new StringBuilder();
      sb.append(card.toString());
      sb.append('[');
      sb.append(index);
      sb.append(',');
      if (visible)
        sb.append('V');
      else
        sb.append('v');
      sb.append(']');
      return sb.toString();
    }
  }

  /**
   * Create a player instance. The identifier passed into this constructor must
   * be globally unique.
   * @param id Unique identifier for this player.
   */
  public Player(int id) {
    this(id, "" + id);
  }

  /**
   * Create a player instance. The identifier passed into this constructor must
   * be globally unique.
   * @param id Unique identifier for this player.
   * @param name Human-readable name for this player.
   */
  public Player(int id, String name) {
    this.id = id;
    this.name = name;
    this.cards = new ArrayList<CardInfo>();
    logger.finest(this + ": created");
  }

  public int getId() {
    return id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    logger.finest(this + ": changing name from '" + this.name
        + "' to '" + name + "'");
    this.name = name;
  }

  public List<Card> getAllCards() {
    List<Card> cardList = new ArrayList<Card>(cards.size());
    for (CardInfo cinfo : cards) {
      cardList.add(cinfo.card);
    }
    return Collections.unmodifiableList(cardList);
  }

  public List<Card> getVisibleCards() {
    List<Card> cardList = new ArrayList<Card>(cards.size());
    for (CardInfo cinfo : cards) {
      if (cinfo.visible)
        cardList.add(cinfo.card);
    }
    return Collections.unmodifiableList(cardList);
  }

  List<CardInfo> getCards() {
    return Collections.unmodifiableList(cards);
  }

  /**
   * Discards the first matching card from the player's hand.
   * @param c Card to match and discard.
   * @return true if a card was found and disacarded.
   */
  boolean discardCard(Card c) {
    for (Iterator<CardInfo> it = cards.iterator(); it.hasNext();) {
      CardInfo ci = it.next();
      if (ci.card.equals(c)) {
        it.remove();
        return true;
      }
    }
    return false;
  }

  /**
   * Discards the card specified.
   * @param ci Card to discard.
   * @return true if a discard occurred.
   */
  boolean discardCard(CardInfo ci) {
    return cards.remove(ci);
  }

  /**
   * Add a card with no public visibility.
   * @param c Card to add.
   */
  void addCard(Card c) {
    CardInfo ci = new CardInfo(c, false);
    addCard(ci);
  }

  /**
   * Add a specific card. The index of the CardInfo will be overwritten.
   * @param ci Card to add.
   */
  void addCard(CardInfo ci) {
    assert ci.getIndex() == -1 : "Already indexed card being re-added.";
    ci.setIndex(++lastCardIndex);
    cards.add(ci);
  }

  /**
   * Clear all cards held by this player.
   */
  void resetCards() {
    cards.clear();
    lastCardIndex = -1;
  }

  public String cardsToString() {
    StringBuilder sb = new StringBuilder();

    for (CardInfo ci : cards) {
      sb.append(ci.toString()).append(',');
    }
    if (!cards.isEmpty()) {
      sb.deleteCharAt(sb.length()-1);
    }

    return sb.toString();
  }

  public String toString() {
    return "Player [id=" + id + ",name=" + name + ",cards=(" + cardsToString() + ")]";
  }

  public int hashCode() {
    return this.id;
  }

  public boolean equals(Object obj) {
    if (obj instanceof Player) {
      return this.id == ((Player)obj).id;
    }
    return false;
  }
}
